/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package main;

import clasificador.ValidadorCruzado;
import datos.Atributo;
import datos.AtributoFinito;
import datos.Dato;
import datos.Muestra;
import datos.MuestraClasificada;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static main.MainEvolveReglas1.getParameters;
import utils.CSVBuilder;

/**
 *
 * @author simply
 */
public abstract class Main {
    protected static PrintStream out = System.out;
    
    protected static Parameter[] getParameters() {
        return new Parameter[]{};
    }
    
    protected static Map<String,Object> readParameters(String[] args)
    {
        Map<String,Object> pars = new HashMap<>();
        for(Parameter p: getParameters())
        {
            pars.put(p.name,getParameter(args,p));
        }
        
        return pars;
    }
    protected static class Parameter<T> {
        public String name;
        public String flag;
        public boolean required = true;
        public String def = null;
        public Class<T> type;
        
        public Parameter name(String s) {
            this.name = s;
            return this;
        }
        public Parameter flag(String s) {
            if( s.startsWith("-"))
                this.flag = s;
            else throw new RuntimeException("Invalid flag name " + s);
            return this;
        }
        public Parameter required(boolean b) {
            this.required = b;
            return this;
        }
        public Parameter defaultsTo(String s) {
            this.def = s;
            this.required = false;
            return this;
        }
        
        public String toString() {
            String str = this.flag + " <";
            if( this.name != null )
                str += this.name;
            else
                str += this.flag;
            str += ">";
            
            if( !this.required ) {
                str = "[" + str + "]";
            }
            return str;
        }
    }

    protected static String parseFilename(String [] args) {
        boolean parsingParameter = false;
        for (String s : args) {
            if (s.startsWith("-")) {
                parsingParameter = true;
            } else if (! parsingParameter) {
                return s;
            } else {
                parsingParameter = false;
            }
        }
        return null;
    }
        
    protected static String parseParameter(String [] args, String flag) {
        boolean foundFlag = false;
        for (String s : args) {
            if (s.equals(flag)) {
                foundFlag = true;
            } else if (foundFlag) {
                return s;
            }
        }
        return null;
    }
    
    protected static <T> T getParameter(String[] args, Parameter<T> p)
    {
        if(p.type.equals(Integer.class))
        {
            return p.type.cast(Integer.parseInt(parseParameter(args,p)));
        } else if(p.type.equals(Double.class)) {
            return p.type.cast(Double.parseDouble(parseParameter(args,p)));
        } else {
            return p.type.cast(parseParameter(args,p));
        }        
    }
        
    protected static String parseParameter(String [] args, Parameter p) {
        boolean foundFlag = false;
        for (String s : args) {
            if (s.equals(p.flag)) {
                foundFlag = true;
            } else if (foundFlag) {
                return s;
            }
        }
        if(!p.required) {
            return p.def;
        } else {
            out.println("Falta parametro " + p.name);
            // printUsage();
            Main.exit(1);
            return null;
        }
    }
    
    protected static List<Atributo<Object>> parseCSVHeader(BufferedReader file) throws IOException {
        List<Atributo<Object>> header = new ArrayList<>();
        String line = file.readLine();
        int lineNumber = 0;
        while (!line.equals("")) {
            header.add(parseAttribute(line, lineNumber));
            lineNumber ++;
            line = file.readLine();
        }
        return header;
    }

    protected static MuestraClasificada parseSample(String filename) throws IOException {
            BufferedReader file = new BufferedReader(new FileReader(filename));
            MuestraClasificada<Object> muestra;

            List<Atributo<Object>> header = parseCSVHeader(file);
            out.println("Validando sobre la muestra " + filename);
            out.println("Atributos: ");
            for (Atributo<Object> atributo : header) {
                out.println("\t" + atributo.getNombre() + " : " + atributo.getTipo());
            }

            /* Esto es feo */
            Atributo<Object> clase = header.get(header.size() - 1);

            List<Atributo<?>> header2 = new ArrayList<Atributo<?>>(header);

            CSVBuilder<Dato, Muestra> builder =
                    new CSVBuilder<>(
                            new Dato.Builder(header),
                            new Muestra.Builder(header2)
                    );

            String line = file.readLine();
            while (line != null) {
                builder.add(line);
                line = file.readLine();
            }

            muestra = new MuestraClasificada<>(builder.finish(), clase);
            out.println("Leidas " + muestra.getDatos().size() + " entradas");
            return muestra;
    }

    private static AtributoFinito<Object> parseFinite(String name, String type, String values, int pos) {
        if (values == null) {
            throw new RuntimeException("Malformed CSV header: no range for finite attribute " + name);
        }

        List<String> valueSet = new ArrayList<>();
        for (String str : values.split("\\|")) {
            valueSet.add(str.trim());
        }
        switch (type) {
            case "string":
                return (AtributoFinito)new AtributoFinito<String>(String.class, name, valueSet, pos) {
                    @Override
                    public String readDato(String s) { return s.trim(); }
                };
            case "int":
                List<Integer> intSet = new ArrayList<>();
                for (String s : valueSet) {
                    intSet.add(Integer.parseInt(s));
                }
                return (AtributoFinito)new AtributoFinito<Integer>(Integer.class, name, intSet, pos) {
                    @Override
                    public Integer readDato(String s) { return Integer.parseInt(s); }
                };
            default:
                throw new RuntimeException("Error parsing CSV header: undefined type " + type);
        }
    }

    private static Atributo<Object> parseInfiniteDiscrete(String name, String type, int pos) {
        switch (type) {
            case "string":
                return (Atributo)new Atributo<String>(String.class, name, pos) {
                    @Override
                    public String readDato(String s) { return s.trim(); }
                };
            case "int":
                return (Atributo)new Atributo<Integer>(Integer.class, name, pos) {
                    @Override
                    public Integer readDato(String s) { return Integer.parseInt(s); }
                };
            default:
                throw new RuntimeException("Error parsing CSV header: undefined type " + type);
        }
    }
    
    private static Atributo<Object> parseAttribute(String s, int pos) {
        String [] tokens = s.split(":");
        if (tokens.length != 2 && tokens.length != 3) {
            throw new RuntimeException("Malformed or empty CSV header");
        }

        String name = tokens[0].trim();
        String type = tokens[1].trim();
        switch (type) {
            case "string":
            case "int":
                if (tokens.length == 3) {
                    return parseFinite(name, type, tokens[2], pos);
                } else {
                    return parseInfiniteDiscrete(name,type, pos);
                }
            case "double":
                return (Atributo)new Atributo<Double> (Double.class, name, pos) {
                    @Override
                    public Double readDato(String s) { return Double.parseDouble(s); }
                };
            default:
                throw new RuntimeException("Cabecera del fichero CSV inválida: tipo desconocido " + type);
        }
    }

    protected static String executableName() {
        return new java.io.File(Main.class.getProtectionDomain()
                .getCodeSource()
                .getLocation()
                .getPath())
                .getName();
    }
    
    protected static void printResults(String filename,
                                     ValidadorCruzado.ResultadosValidacionCruzada resultados) {

        out.println(
                "Resultados para la muestra " + filename
        );
        int particion = 1;
        Double suma = 0.;
        for (Double error : resultados.getErrores()) {
            suma += error;
            out.println("\tParticion de test " + particion);
            out.println("\t\tProporcion de errores: " + error);
            out.println("\t\tProporcion de aciertos: " + (1.0 - error));
            particion++;
        }
        out.println("Error medio: " + (suma / resultados.getErrores().size()));
    }
    
    protected static void printUsage(Parameter[] parameters) {
        out.println("Uso:");
        String str = "\tjava -jar " + Main.executableName() + " <muestra>";
        for( Parameter p : parameters ) {
            str += " " + p;
        }
        out.println(str);
    }
    
    protected static void exit(int k) {
        System.exit(k);
    }    
}
